/****************************************************************************
 * Copyright 2010 Huascar A. Sanchez                                        *
 *                                                                          *
 * Licensed under the Apache License, Version 2.0 (the "License");          *
 * you may not use this file except in compliance with the License.         *
 * You may obtain a copy of the License at                                  *
 *                                                                          *
 *     http://www.apache.org/licenses/LICENSE-2.0                           *
 *                                                                          *
 * Unless required by applicable law or agreed to in writing, software      *
 * distributed under the License is distributed on an "AS IS" BASIS,        *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
 * See the License for the specific language governing permissions and      *
 * limitations under the License.                                           *
 ****************************************************************************/
package com.gotobject.greatq;

import com.gotobject.greatq.datastore.DatastoreDriver;
import com.gotobject.greatq.datastore.DatastoreStatement;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.servlet.ServletRequestContext;
import org.jiql.jdbc.ResultSet;
import tools.util.NameValuePairs;
import tools.util.StringUtil;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;

import static com.gotobject.filter.internal.ToStrings.str;

/**
 * @author huascar.sanchez@gmail.com (Huascar A. Sanchez)
 */
public class GreatQServlet extends HttpServlet {
    private static class CarryingOverData {
        String username   = null;
        String password   = null;
        int    maxUpload  = 10000000;
        String sql        = null;
        String dateformat = null;
    }

    private final AtomicReference<CarryingOverData> wildCard = new AtomicReference<CarryingOverData>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        final ServletContext    context = config.getServletContext();
        final String            props   = context.getRealPath("/WEB-INF/greatq.properties");
        // guard condition that checks if we have a properties file
        if(!isFound(props)) return;

        wildCard.compareAndSet(wildCard.get(), createWildcard(props));
        super.init(config);
        
    }


    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        final ServletRequestContext     servletRequestContext   = new ServletRequestContext(req);
        final boolean                   isMultipart             = FileUpload.isMultipartContent(servletRequestContext);
        final Hashtable<String, Object> directValues            = new Hashtable<String, Object>();
        final CarryingOverData          carryingOverData        = new CarryingOverData();
        final CarryingOverData          wc                      = wildCard.get();

        handleMultipartMethod(req, isMultipart, directValues, carryingOverData, wc);


        if (carryingOverData.username == null){
            carryingOverData.username = req.getParameter("user");
        }

		if (carryingOverData.password == null){
            carryingOverData.password = req.getParameter("password");
        }

		if (!StringUtil.isRealString(carryingOverData.username) || !StringUtil.isRealString(carryingOverData.password)) {
			resp.sendError(403,"Invalid User or Password");
			return;
		}

		if (!StringUtil.isRealString(wc.username) || !StringUtil.isRealString(wc.password)) {
			resp.sendError(403,"Invalid User OR Password");
			return;
		}

        methodX(req, resp, directValues, carryingOverData, wc);
    }

    private void methodX(
            HttpServletRequest req, HttpServletResponse resp, Hashtable<String, Object> directValues,
            CarryingOverData carryingOverData, CarryingOverData wc
    ) throws IOException {
        final Hashtable<String, Object> requestContent = new Hashtable<String, Object>();
        requestContent.put("remote","true");
        Connection connection = null;

        try {
            NameValuePairs nameValuePairs = updateCallbackFields(req, resp, carryingOverData, wc);
            if (nameValuePairs == null) return;

            connection  =  createConnection(nameValuePairs);
            configureCallback(directValues, carryingOverData, requestContent, connection);
            sendThruWire(resp, requestContent);
        } catch (Exception e){
            //noinspection ThrowableInstanceNeverThrown
            requestContent.put("error", new SQLException(e));
            sendThruWire(resp, requestContent);
        } finally {
            if(connection != null){
                try {
                    connection.close();
                } catch (SQLException e) {
                    // do nothing
                }
            }
        }
    }

    private NameValuePairs updateCallbackFields(HttpServletRequest req, HttpServletResponse resp, CarryingOverData carryingOverData, CarryingOverData wc) throws IOException {
        if(!carryingOverData.username.equals(wc.username) || !carryingOverData.password.equals(wc.password)){
            resp.sendError(403,"Invalid User OR Invalid Password");
            return null;
        }

        if(carryingOverData.sql == null){
            carryingOverData.sql =  req.getParameter("query");
        }

        final NameValuePairs nameValuePairs = new NameValuePairs();
        if(carryingOverData.dateformat == null){
            carryingOverData.dateformat = req.getParameter("date.format");
        }

        if(carryingOverData.dateformat != null){
            nameValuePairs.put("date.format",carryingOverData.dateformat);
        }
        return nameValuePairs;
    }

    private void configureCallback(Hashtable<String, Object> directValues, CarryingOverData carryingOverData, Hashtable<String, Object> requestContent, Connection connection) throws SQLException {
        final DatastoreStatement statement   = (DatastoreStatement) connection.createStatement();
        statement.setDirectValues(directValues);
        statement.execute(carryingOverData.sql);
        final ResultSet resultSet = statement.getResultSet();
        if(resultSet != null) {
            if (resultSet.getResults() != null) {
                requestContent.put("results",resultSet.getResults());
            }

            if (resultSet.getSQLParser() != null) {
                requestContent.put("sqlparser", resultSet.getSQLParser());
            }
        } else {
            requestContent.put("sqlparser", statement.getSQLParser());
        }
    }


    private static void sendThruWire(HttpServletResponse resp, Hashtable<String, Object> requestContent) throws IOException {
            resp.setContentType("binary/object");
            final OutputStream          fos = resp.getOutputStream();
            final ObjectOutputStream    oos = new ObjectOutputStream(fos);
			oos.writeObject(requestContent);
    }

    private void handleMultipartMethod(
            HttpServletRequest req,
            boolean multipart,
            Hashtable<String, Object> directValues,
            CarryingOverData carryingOverData,
            CarryingOverData wc) {
        if(multipart){
            final ServletFileUpload upload  = new ServletFileUpload();
            upload.setSizeMax(wc.maxUpload);
            try {
                final FileItemIterator iterator = upload.getItemIterator(req);
                while(iterator.hasNext()){
                    final FileItemStream each   = iterator.next();
                    final String         name   = each.getName();
                    final InputStream stream = each.openStream();
                    if("directValues".equalsIgnoreCase(name)){
                        final ObjectInputStream ois = new ObjectInputStream(stream);
                        if(directValues.isEmpty()) {
                            //noinspection unchecked
                            directValues.putAll((Hashtable)ois.readObject());
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();  //todo(Huascar)
            }

            final Hashtable params = (Hashtable)directValues.get("parameters");
            if(params != null){
                final Enumeration key = params.elements();
                while(key.hasMoreElements()){
                    final String eachObj   = str(key.nextElement());
                    final String eachValue = str(params.get(eachObj));
                    if("query".equalsIgnoreCase(eachObj)){
                        carryingOverData.sql = eachValue;
                    } else if("password".equalsIgnoreCase(eachObj)) {
                        carryingOverData.password = eachValue;
                    } else if("user".equalsIgnoreCase(eachObj)) {
                        carryingOverData.username = eachValue;
                    } else if("date.format".equalsIgnoreCase(eachObj)) {
                        carryingOverData.dateformat = eachValue;
                    }
                }
            }
        }
    }


    private static CarryingOverData createWildcard(String filename) {
        final CarryingOverData result = new CarryingOverData();
        try {
            @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"})
            final NameValuePairs parser = new NameValuePairs(filename);
            if((!parser.contains("user") && !parser.contains("user"))) return result;
            result.username = (String) parser.get("user");
            result.password = (String) parser.get("password");
            if(parser.getInt("maxUpload") > 0) {
                result.maxUpload = parser.getInt("maxUpload");
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return result;
    }

    private static boolean isFound(String filename) {
         return new File(filename).exists();
    }


    public static Connection createConnection(NameValuePairs nameValuePairs) {
        try {
            final Properties props = new Properties(){{
                put("user","");
                put("password","");
            }};

            final Class<?> clazz = Class.forName("com.gotobject.greatq.datastore.DatastoreDriver");
            final DatastoreDriver driver = (DatastoreDriver) clazz.newInstance();

            nameValuePairs.merge(props);
            return driver.connect(
                    "greatq://local",
                    nameValuePairs.toProperties()
            );
    	} catch (Exception e){
            throw new RuntimeException(e);
    	}
    }

}
